package handler

import (
	"bufio"
	"fmt"
	"gopatch/file"
	"gopatch/git"
	"gopatch/global"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// 日期格式常量
const timePattern_normal = "2006-01-02 15:04:05"

var handlerImpls = make([]handleGit, 0)

// 注册所有实现类
func init() {
	handlerImpls = append(handlerImpls, &normalHandler{})
	handlerImpls = append(handlerImpls, &autoCompareHandler{})
}

// 定义处理补丁的接口
type handleGit interface {
	// 抉择
	judgeHandler() bool

	// 选择需要的补丁文件
	chooseFile(gitUpdateFile, filenameWithoutExt string, sameNameFiles []file.FileStruct) []file.FileStruct
}

// 获取处理类
func getHandler() handleGit {
	for _, impl := range handlerImpls {
		if impl.judgeHandler() {
			return impl
		}
	}
	return nil
}

// HandleUpdateFiles 处理需要更新的文件
func HandleUpdateFiles(allUpdateFiles map[string][]git.GitVersionStruct) []file.FileStruct {

	gHandler := getHandler()
	if gHandler == nil {
		panic("无法获取对应的处理类")
	}

	// 最终需要的补丁文件
	var allTargetFiles []file.FileStruct

	// 处理需要更新的文件
	for updateFile, gitVersions := range allUpdateFiles {
		// 获取文件名
		name := filepath.Base(updateFile)
		// 获取扩展名
		ext := filepath.Ext(name)
		// 去除扩展名
		filenameWithoutExt := name[:len(name)-len(ext)]

		if len(filenameWithoutExt) == 0 {
			// 去除扩展名后文件名为空，直接使用文件名
			filenameWithoutExt = name
		}

		sameNameFiles := global.GetSameNameFiles(filenameWithoutExt)

		// 编译后产物没有对应的文件名
		if sameNameFiles == nil {
			global.AppendMsg(global.ERROR, fmt.Sprintf("文件 %s 没有找到对应的", filenameWithoutExt))
			continue
		}

		// 找到了所有同名（去除扩展名）的文件，进行选择
		chooseFiles := gHandler.chooseFile(updateFile, filenameWithoutExt, sameNameFiles)

		if chooseFiles == nil || len(chooseFiles) == 0 {
			continue
		} else {
			// 已存在的目标文件不放入
			for _, chooseFile := range chooseFiles {
				if !file.Contains(allTargetFiles, chooseFile) {
					allTargetFiles = append(allTargetFiles, chooseFile)
				}
			}
		}

		// 查相关的扩展文件
		var allExternFiles []file.FileStruct
		// 根据已选中的文件来查找扩展名文件
		for _, chooseFile := range chooseFiles {
			extendFiles := searchExtendFile(chooseFile.AbsFilePath, chooseFile.GetFilenameWithoutExt())
			allExternFiles = append(allExternFiles, extendFiles...)
		}

		// 已存在的文件不放入
		for _, externFile := range allExternFiles {
			if !file.Contains(allTargetFiles, externFile) {
				allTargetFiles = append(allTargetFiles, externFile)
			}
		}

		var lastModFile file.FileStruct
		for _, chooseFile := range chooseFiles {
			// 遍历所有被选择文件，得到最新的修改时间
			modTime := chooseFile.GetModTimeStr(timePattern_normal)

			if lastModFile.IsZero() {
				lastModFile = chooseFile
			}

			compare := strings.Compare(modTime, lastModFile.GetModTimeStr(timePattern_normal))
			if compare > 0 {
				lastModFile = chooseFile
			}
		}

		var lastGitVersion git.GitVersionStruct
		// 再去找这个文件涉及的修订号中最新的提交时间
		for _, gitVersion := range gitVersions {
			time := gitVersion.GetCommitTime(timePattern_normal)
			if lastGitVersion.IsZero() {
				lastGitVersion = gitVersion
			}
			compare := strings.Compare(time, lastGitVersion.GetCommitTime(timePattern_normal))
			if compare > 0 {
				lastGitVersion = gitVersion
			}
		}

		// 判断最新修改文件的修改时间是否在最新提交记录的提交时间之后
		if !lastGitVersion.IsZero() && !lastModFile.IsZero() {
			commitTimeStr := lastGitVersion.GetCommitTime(timePattern_normal)
			modTimeStr := lastModFile.GetModTimeStr(timePattern_normal)

			compare := strings.Compare(modTimeStr, commitTimeStr)
			if compare < 0 {
				global.AppendMsg(global.WARN, fmt.Sprintf("文件 %s 修改时间(%s)在相关提交记录(%s)之前，请确认是否进行编译;\n\t\tfile:%s, version:%s",
					lastModFile.GetFileName(), modTimeStr, commitTimeStr, lastModFile.AbsFilePath, lastGitVersion.GitVersion))
			}

		}

	}

	return allTargetFiles
}

// 根据已选择的补丁查找扩展文件
// eg: .class 的内部类 $1.class
func searchExtendFile(selectFileAbsPath, filenameWithoutExt string) []file.FileStruct {

	var extendFiles []file.FileStruct

	// 根据选择的文件去查询 文件名+$ 的文件是否存在
	selectFileBaseDirPath := filepath.Dir(selectFileAbsPath)
	dir, err := os.ReadDir(selectFileBaseDirPath)
	if err != nil {
		global.AppendMsg(global.ERROR, fmt.Sprintf("查找扩展文件时,文件目录[%s]不存在", selectFileBaseDirPath))
		return nil
	}
	for _, entry := range dir {
		if entry.IsDir() {
			continue
		}

		// .class 文件需要查找 $n.class
		if strings.HasSuffix(selectFileAbsPath, ".class") && strings.Index(entry.Name(), filenameWithoutExt+"$") == 0 {
			fileT := file.FileStruct{
				AbsFilePath: filepath.Join(selectFileBaseDirPath, entry.Name()),
			}
			extendFiles = append(extendFiles, fileT)
		}
	}

	return extendFiles
}

// 处理输入内容
func handleInput(filenameWithoutExt string, sameNameFiles []file.FileStruct) []file.FileStruct {

	var updTargetAbsPath []file.FileStruct

	// inputStr := ""
	log.Printf("\n-- %s 存在同名文件\n", filenameWithoutExt)
	for idx, sameNameFile := range sameNameFiles {
		log.Printf("-------%d: %s\n", idx, strings.Replace(sameNameFile.AbsFilePath,
			global.GetProperty().TargetPath, "", 1))
	}

	for {
		var selectIdxs []int64

		log.Printf("\n-- 请选择(多选使用空格分隔, 输入'n'取消本次)\n")
		stdin := bufio.NewReader(os.Stdin)
		inputStr, err := stdin.ReadString('\n')
		if err != nil {
			log.Printf("输入错误，请重新输入！%v\n", err)
			continue
		}
		// 去除末尾的换行符
		inputStr = strings.TrimSuffix(inputStr, "\n")
		re := regexp.MustCompile(`\s+`)
		idxsStr := re.Split(inputStr, -1)

		// 发生错误
		errExist := false
		// 取消本次
		cancleInput := false

		// 处理输入内容
		for _, idxStr := range idxsStr {
			if len(idxStr) == 0 {
				continue
			}
			// 跳过本次选择
			if idxStr == "n" {
				cancleInput = true
				break
			}

			idx, err := strconv.ParseInt(idxStr, 10, 64)
			if err != nil {
				log.Printf("请输入数字%v\n", err)
				errExist = true
				break
			}
			if idx < 0 || idx >= int64(len(sameNameFiles)) {
				log.Printf("请输入正确范围的数字\n")
				errExist = true
				break
			}
			selectIdxs = append(selectIdxs, idx)
		}

		// 1. 取消了
		if cancleInput {
			log.Printf("已取消 %s 文件的选择\n", filenameWithoutExt)
			global.AppendMsg(global.ERROR, fmt.Sprintf("文件 %s 取消了补丁选择，请留意", filenameWithoutExt))
			break
		}
		// 2. 出现错误
		if errExist {
			continue
		}
		// 3. 没有输入的内容
		if len(selectIdxs) <= 0 {
			log.Printf("没有输入的数字，取消请输入'n'\n")
			continue
		}

		for _, selectIdx := range selectIdxs {
			sameNameFile := sameNameFiles[selectIdx]
			updTargetAbsPath = append(updTargetAbsPath, sameNameFile)
		}

		break
	}

	return updTargetAbsPath
}
